package com.merry.spring5.aop.annotation;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author ybzou
 * 切面类
 * @Aspect：表示一个切面类
 */
@Component
@Aspect
public class LogAspect {
    /*
     * 设置切入点和通知类型
     *      通知类型：前置、返回、异常、后置、环绕通知
     *          前置： @Before()
     *          返回： @AfterReturning
     *          异常： @AfterThrowing
     *          后置： @After()
     *          环绕： @Around()
     *      所有的通知注解都有一个共有的属性 value()。这里面需要填写一个切入点表达式：
     *      切入点表达式：execution(访问修饰符 增强方法返回类型 增强方法全路径.方法名称(方法参数列表))
     *      环绕通知相当于其他四种通知的组合体：这是由于在其他四种通知中只能使用JoinPoint（也可以不添加入参）这个入参，而这个类只可以获取一些方法的基本信息，比如方法名，参数等。
     *      但是在环绕通知中“只能使用且必须使用”ProceedingJoinPoint这个类作为入参，如果不使用这个，idea会出现警告。这个类中有一个proceed()方法，这个方法就相当于调用了目标方法。
     *      当我在四种通知中使用PJP时：ProceedingJoinPoint as first parameter is allowed only in @Around advices
     *      当我在环绕通知中没有使用PJP或者使用了JP时：Advice call cannot proceed: probably you forgot to declare a ProceedingJoinPoint parameter
     */

    @Before( value = "execution(public int com.merry.spring5.aop.annotation.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知... "+":参数... "+args[0]+"。"+args[1]);
    }

    @After(value = "execution(public int com.merry.spring5.aop.annotation.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知... "+" : 方法名字"+methodName);
    }

    /**
     * 异常通知：通过throwing属性获取异常信息
     * 异常通知在后置通知之前执行
     * @param joinPoint
     */
    @AfterThrowing(value = "execution(public int com.merry.spring5.aop.annotation.CalculatorImpl.*(..))",throwing = "exception")
    public void afterThrowMethod(JoinPoint joinPoint,Exception exception){
        System.out.println("异常通知...出现异常的方法："+joinPoint.getSignature().getName()+" 异常信息："+exception.getMessage());
    }

    /**
     * 返回通知和后置通知的最大区别在于，返回通知可以得到增强方法的返回结果。
     * 返回通知在后置通知之后执行。
     * 它是通过返回通知中的returning属性实现的。
     * @param joinPoint
     */
    @AfterReturning(value = "execution(public int com.merry.spring5.aop.annotation.CalculatorImpl.*(..))"
        ,returning = "result")
    public void afterReturnMethod(JoinPoint joinPoint,Object result){
        System.out.println("返回通知...增强的方法是："+joinPoint.getSignature().getName()
        +" 返回结果是："+result);
    }

    /**
     * 环绕通知：可以在方法执行前后，或者出现异常，返回结果时通知。
     */
    @Around(value = "execution(public int com.merry.spring5.aop.annotation.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String arg = Arrays.toString(args);
        Object result=null;
        try {
            System.out.println("环绕通知...==目标方法之前执行...");
            //调用目标方法
            result = joinPoint.proceed();
            System.out.println("环绕通知...==目标方法之后执行...");
        }catch (Throwable throwable){
            System.out.println(throwable.getMessage());
            System.out.println("环绕通知...==目标方法出现异常时执行...");
        }finally {
            System.out.println("环绕通知...==目标方法执行完毕执行...");
        }
        return result;
    }

    /**
     * 重用切入点表达式
     * Pointcut
     */
    @Pointcut(value = "execution(public int com.merry.spring5.aop.annotation.CalculatorImpl.*(..))")
    private void pointCut(){

    }

    /**
     * value = "pointCut()"，通过这个参数重用切入点表达式
     * @Order(value = 1)：设置切面的优先级，值越小优先级越高。
     * @param joinPoint
     * @param result
     */
    @Order(value = 1)
    @AfterReturning(value = "pointCut()",returning = "result")
    public void reusePointCut(JoinPoint joinPoint,Object result){
        System.out.println("重用切入点表达式的返回通知...");
    }
}
